# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import socket

import _btsocket
import constants

class BluetoothSocket(socket._socketobject):
    """Socket wrapper class for Bluetooth sockets.

    Wraps the Python socket class to implement the necessary pieces to be able
    to bind to the HCI monitor and control sockets as well as receive messages
    with ancillary data from them.

    The bind() and connect() functions match the behavior of those of the
    built-in socket methods and accept a tuple defining the address, the
    contents of which depend on the protocol of the socket.

    For BTPROTO_HCI this is:
      @param dev: Device index, or HCI_DEV_NONE.
      @param channel: Channel, e.g. HCI_CHANNEL_RAW.

    For BTPROTO_L2CAP this is:
      @param address: Address of device in string format.
      @param psm: PSM of L2CAP service.

    For BTPROTO_RFCOMM this is:
      @param address: Address of device in string format.
      @param cid: Channel ID of RFCOMM service.

    For BTPROTO_SCO this is:
      @param address: Address of device in string format.

    """

    def __init__(self,
                 family=constants.AF_BLUETOOTH,
                 type=socket.SOCK_RAW,
                 proto=constants.BTPROTO_HCI):
        super(BluetoothSocket, self).__init__(family, type, proto)

    def bind(self, *args):
        """Bind the socket to a local address."""
        if self.family == constants.AF_BLUETOOTH:
            return _btsocket.bind(self, self.proto, *args)
        else:
            return super(BluetoothSocket, self).bind(*args)

    def connect(self, *args):
        """Connect the socket to a remote address."""
        if self.family == constants.AF_BLUETOOTH:
            return _btsocket.connect(self, self.proto, *args)
        else:
            return super(BluetoothSocket, self).connect(*args)

    def recvmsg(self, bufsize, ancbufsize=0, flags=0):
        """Receive normal data and ancillary data from the socket.

        @param bufsize size in bytes of buffer for normal data.
        @param ancbufsize size in bytes of internal buffer for ancillary data.
        @param flags same meaning as socket.recv()

        @return tuple of (data, ancdata, msg_flags, address),
          @ancdata is zero or more tuples of (cmsg_level, cmsg_type, cmsg_data).

        """
        buffer = bytearray(bufsize)
        (nbytes, ancdata, msg_flags, address) = \
                self.recvmsg_into((buffer,), ancbufsize, flags)
        return (bytes(buffer), ancdata, msg_flags, address)

    def recvmsg_into(self, buffers, ancbufsize=0, flags=0):
        """Receive normal data and ancillary data from the socket.

        @param buffers iterable of bytearray objects filled with read chunks.
        @param ancbufsize size in bytes of internal buffer for ancillary data.
        @param flags same meaning as socket.recv()

        @return tuple of (nbytes, ancdata, msg_flags, address),
          @ancdata is zero or more tuples of (cmsg_level, cmsg_type, cmsg_data).

        """
        return _btsocket.recvmsg(self, buffers, ancbufsize, flags)


def create_le_gatt_server_socket():
    """Create a socket for incoming BLE connection.

    The function creates an LE socket, then does bind, listen and accept.
    The accept call blocks until it receives an incoming connection.

    Note, that resulting socket will be standard Python socket,
    not BluetoothSocket. It is already connected, ready for recv() and
    send() calls, and there is no need for further setup.

    Before calling this function, the machine must be set up (with appropriate
    HCI commands) to be advertising, be powered, and have LE turned on.

    @return tuple of (sock, source_address),
      @sock is standard Python socket object, which is able to receive and send.
      @source_address is string containing BDADDR of the connected remote.

    """
    file_descriptor, source_address = _btsocket.listen_and_accept()
    sock = socket.fromfd(file_descriptor, constants.AF_BLUETOOTH,
                         socket.SOCK_SEQPACKET, constants.BTPROTO_L2CAP)
    return sock, source_address
